//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
// Copyright 2012 Microsoft Corporation. All Rights Reserved
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.   
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
//--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

namespace Preflight.Verifications
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using System.ComponentModel.Composition.Hosting;
    using System.ComponentModel.Composition.Primitives;
    using System.Text.RegularExpressions;

    using Preflight.Core;
    using Preflight.Verifications.Implementations;

    public class VerificationBuilder : IVerificationBuilder
    {
        private static readonly IVerificationBuilder instance = CreateBuilder();
        private readonly AggregateCatalog aggregateCatalog = new AggregateCatalog();
        private CompositionContainer container;

        private VerificationBuilder()
        {
            this.DescriptionValidationEnabled = true;
        }

        public static IVerificationBuilder Instance
        {
            get { return instance; }
        }

        internal bool DescriptionValidationEnabled
        {
            get;
            set;
        }
      
        public void AddVerificationCatalog(ComposablePartCatalog catalog)
        {
            Log.LogInformation("Added catalog for verification probe of type: {0}", catalog.GetType().FullName);
            this.aggregateCatalog.Catalogs.Add(catalog);
        }

        public IVerification BuildVerification<TDescription>(TDescription description) where TDescription : VerificationDescription
        {
            if (description == null)
            {
                Log.LogError("Cannot build verification. Description is null");
                throw new ArgumentNullException("description");
            }

            this.ValidateDescription(description);
            var export = this.GetVerificationExport(description);
            var verification = BuildVerification(description, export);
            return verification;
        }

        public IVerification SafeBuildVerification<TDescription>(TDescription description) where TDescription : VerificationDescription
        {
            try
            {
                return this.BuildVerification(description);
            }
            catch (VerificationBuilderException exception)
            {
                Log.LogInformation("Safe Build Verification has returned a static result verification");
                return new StaticResultVerification(exception, "Failed to build verification", description.VerificationName, description.Category);
            }
        }

        private Lazy<VerificationBase<TDescription>> GetVerificationExport<TDescription>(TDescription description) where TDescription : VerificationDescription
        {
            Lazy<VerificationBase<TDescription>> export;
            var expectedVerificationType = typeof(VerificationBase<TDescription>).FullName;
            Log.LogInformation("Looking for verification of type: {0}", expectedVerificationType);

            try
            {
                export = this.container.GetExport<VerificationBase<TDescription>>();
            }
            catch (ImportCardinalityMismatchException exception)
            {
                LogError(expectedVerificationType, exception);

                if (IsDuplicateRegistrationException(exception))
                {
                    throw new DuplicateVerificationRegistrationException(description, exception);
                }

                throw new VerificationNotFoundException(description, exception);
            }

            return export;
        }

        private static void LogError(string expectedVerificationType, Exception exception)
        {
            Log.LogError("Error building verification of type: {0}. Error Details: {1}", expectedVerificationType, exception);
        }

        private static bool IsDuplicateRegistrationException(ImportCardinalityMismatchException exception)
        {
            return Regex.IsMatch(exception.Message, "more than one", RegexOptions.IgnoreCase);
        }

        private void ValidateDescription<TDescription>(TDescription description) where TDescription : VerificationDescription
        {
            if (this.DescriptionValidationEnabled)
            {
                this.ValidateDescription((VerificationDescription)description);
            }
            else
            {
                Log.LogWarning("Description validation is disabled.");
            }
        }

        private static VerificationBase<TDescription> BuildVerification<TDescription>(TDescription description, Lazy<VerificationBase<TDescription>> export)
            where TDescription : VerificationDescription
        {
            object verificationInstance;
            var expectedVerificationType = typeof(VerificationBase<TDescription>).FullName;

            try
            {
                verificationInstance = Activator.CreateInstance(export.Value.GetType());
            }
            catch (TypeInitializationException exception)
            {
                LogError(expectedVerificationType, exception);
                throw new VerificationFailedToInitializeException(description, exception);
            }
            catch (CompositionException exception)
            {
                LogError(expectedVerificationType, exception);
                throw new VerificationFailedToInitializeException(description, exception);
            }

            var verification = verificationInstance as VerificationBase<TDescription>;

            if (verification != null)
            {
                verification.Initialize(description);
            }
            else
            {
                Log.LogError("Verification could not be converted to type: {0}", expectedVerificationType);
            }

            return verification;
        }

        private void ValidateDescription(VerificationDescription description)
        {
            try
            {
                description.Validate();
            }
            catch (Exception exception)
            {
                throw new InvalidDescriptionException(description, exception);
            }
        }

        private void InitializeContainer()
        {
            if (this.container != null)
            {
                return;
            }

            this.container = new CompositionContainer(this.aggregateCatalog);
        }

        private static IVerificationBuilder CreateBuilder()
        {
            var catalog = new TypeCatalog(GetVerificationTypes());
            Log.LogInformation("Creating new verification builder...");
            var builder = new VerificationBuilder();
            builder.aggregateCatalog.Catalogs.Add(catalog);
            builder.InitializeContainer();
            return builder;
        }

        private static IEnumerable<Type> GetVerificationTypes()
        {
            yield return typeof(CertificateVerification);
            yield return typeof(ConfigurationVerification);
            yield return typeof(FileSystemAccessVerification);
            yield return typeof(SqlConnectionVerification);
            yield return typeof(WebGetVerification);
        }
    }
}
